package mop.model.task;

import java.sql.Timestamp;
import java.util.Date;
import java.util.List;

import mop.exception.BusinessRule1Exception;
import mop.exception.BusinessRule3Exception;
import mop.model.World;
import mop.util.Memento;

/**
 * Class representing Schedule of a task
 * 
 * @invar Business rule 1 is met
 *  | hasValidDeadline()
 * 	
 * @author MOP Team 7
 */
public class Schedule implements ScheduleInterface,Memento<ScheduleMemento>
{
	
	/**
	 * The associated task
	 */
	private final Task task;
	
	/**
	 * Constructor for Schedule class
	 * 
	 * @param t			The associated task
	 * @param start 	The start time
	 * @param dur		The task duration
	 * @param deadline	The task deadline
	 * 
	 * @throws BusinessRule1Exception
	 * 		| @see #setDates(Timestamp, Timestamp, Timestamp)
	 * @throws BusinessRule3Exception 
	 * 		| @see #setDates(Timestamp, Timestamp, Timestamp)
	 * 
	 * @post	Task is set to given task
	 * 	|this.task == t
	 * 
	 * @effect Dates are set
	 * 	|setDates(start,dur,deadline)
	 */
	public Schedule(Task t,Timestamp start,Timestamp dur,Timestamp deadline) 
		throws BusinessRule3Exception, BusinessRule1Exception
	{
		this.task = t;
		setDates(start,dur,deadline);
	}

	
	/**
	 * Constructor for Schedule class
	 * 
	 * @param start 	The start time
	 * @param dur		The task duration
	 * @param deadline	The task deadline
	 *
	 * @throws BusinessRule1Exception
	 * 	|!hasValidDeadline()
	 * @throws BusinessRule3Exception 
	 * 	|!task.forceGetStatus().rule3isMet()
	 *
	 * @post Start date is set to given start
	 * 	|getStart() == start
	 * @post Duration date is set to given duration
	 * 	|getDuration() == duration
	 * @post Deadline is set to given deadline
	 * 	|getDeadline() == deadline
	 */
	public void setDates(Timestamp start, Timestamp dur, Timestamp deadline) 
		throws BusinessRule3Exception, BusinessRule1Exception
	{
		ScheduleMemento m = saveMemento();
		this.start = start;
		this.duration = dur;
		this.deadline = deadline;
		
		if(!hasValidDeadline()){
			restoreMemento(m);
			throw new BusinessRule1Exception();
		}
		
		if(task.forceGetStatus() != null && !task.forceGetStatus().rule3isMet()){
			restoreMemento(m);
			throw new BusinessRule3Exception(task);
		}

	}
	
	/*************************************************************************** 
	 * 				Business rule 1 constraint checking
	 ***************************************************************************/
	 
	/**
	 * Check whether a given date is valid according to business rule 1
	 * @param start			Start date
	 * @param duration		Duration
	 * @param deadline		Deadline
	 * @param dependencies	List with dependencies to check business rule 1
	 * @param parents		List with parents to check business rule 1
	 * 
	 * @return True if date is valid according to business rule 2
	 */
	public static boolean isValidDeadline(Timestamp start, Timestamp duration, Timestamp deadline, List<Task> dependencies, List<Task> parents)
	{
		if(deadline.getTime() < getEarliestEndTime(start,duration,dependencies).getTime())
			return false;
		
		for(Task p : parents)
		{
			if(!p.getSchedule().hasValidDeadline())
				return false;
		}
		return true;
	}
	
	/**
	 * @see ScheduleInterface#hasValidDeadline()
	 */
	public boolean hasValidDeadline()
	{
		return isValidDeadline(getStart(),getDuration(),getDeadline(),task.getDependencies(),task.getParents());
	}
	 
	/*************************************************************************** 
	 * 					Schedule start date
	 ***************************************************************************/

	/**
	 * @see ScheduleInterface#getStart()
	 */
	public Timestamp getStart()
	{
		return this.start;
	}
	
	/**
	 * Set the start date
	 * 
	 * @param start	The start date for the schedule
	 * @effect set duration
	 * 	|setDates(start,getDuration(), getDeadline())
	 */
	 public void setStart(Timestamp start) 
	 	throws BusinessRule3Exception, BusinessRule1Exception
	 {
		this.setDates(start, getDuration(), getDeadline());
	 }
	
	 /**
	  * Variable for the start date of the project
	  */
	private Timestamp start;
	
	
	/***************************************************************************
	 * 							Schedule duration
	 ***************************************************************************/
	/**
	 * @see ScheduleInterface#getDuration()
	 */
	public Timestamp getDuration()
	{
		return this.duration;
	}
	
	/**
	 * Set the expected duration
	 * 
	 * @param duration	The expected duration for the schedule
	 * @effect set duration
	 * 	|setDates(getStart(), dur, getDeadline())
	 */
	 public void setDuration(Timestamp dur) 
	 	throws BusinessRule3Exception, BusinessRule1Exception
	 {
		 this.setDates(getStart(), dur, getDeadline());
	 }
	
	/**
	 * Variable for the expected duration of the schedule
	 */
	private Timestamp duration;
	
	/***************************************************************************
	 * 							Schedule deadline
	 ***************************************************************************/
	/**
	 * @see ScheduleInterface#getDeadline()
	 */
	public Timestamp getDeadline()
	{
		return this.deadline;
	}
	
	/**
	 * Set the deadline
	 * 
	 * @param deadline	The deadline for the task
	 *  
	 * @effect set deadline
	 * 	|setDates(getStart(), getDuration(), deadline)
	 */
	 public void setDeadline(Timestamp deadline) 
	 	throws BusinessRule3Exception, BusinessRule1Exception
	 {
		 this.setDates(getStart(), getDuration(), deadline);
	 }
	
	/**
	 * variable for the deadline of the project
	 */
	private Timestamp deadline;
	
	/**
	 * @see ScheduleInterface#getEarliestEndTime()
	 */
	public Timestamp getEarliestEndTime()
	{
		return getEarliestEndTime(getStart(),getDuration(),task.getDependencies());
	}
	
	/**
	 * Returns earliest possible endTime
	 * 
	 * @Fix changed task to t to avoid problems
	 * 
	 * @return Earliest possible end time calculated as: latest(start,earliestEndTime(dependencies)) + duration
	 */
	private static Timestamp getEarliestEndTime(Timestamp start, Timestamp duration, 
			List<Task> dep)
	{
		long time = 0;
		
		for(Task t : dep)
		{
			if(t.getSchedule().getEarliestEndTime().getTime() > time)
				time = t.getSchedule().getEarliestEndTime().getTime();
		}
		
		time = Math.max(time,start.getTime()) + duration.getTime();
		
		return new Timestamp(time);
	}
	
	/***************************************************************************
	 * 						Other usefull methods
	 ***************************************************************************/
	/**
	 * Checks whether current time is in period
	 * @return false if the current time is not between start and end
	 */
	public boolean inPeriod()
	{
		Date c = World.getTime();
		return this.getStart().before(c) && this.getDeadline().after(c);
	}
	
	/***************************************************************************
	 * 						Memento pattern Support
	 ***************************************************************************/
	
	/**
	 * Save current schedule instance to a memento
	 * @return	ScheduleMemento with current state
	 */
    public ScheduleMemento saveMemento() {
        return new ScheduleMemento(getStart(),getDuration(),getDeadline());
    }
 
    /**
     * Restore to a given memento
     * @param m	ScheduleMemento to restore to
     * @effect	All attributes are set to original value
     */
    public void restoreMemento(ScheduleMemento memento) {
        this.duration = memento.getDuration();
        this.start = memento.getStart();
        this.deadline = memento.getDeadline();
    }

}
